package http_client

import (
	"context"
	"errors"
	"git.mycaigou.com/gfyx/common/authorize"
	"git.mycaigou.com/gfyx/common/cache"
	"git.myscrm.cn/golang/stark/v4"
	"github.com/goinggo/mapstructure"
	"github.com/parnurzeal/gorequest"
	"os"
	"strconv"
	"strings"
)

const (
	GFYX_MODEL_API_HOST         = "gfyx_model_api_host"
	GFYX_MODEL_TOKEN_URI        = "gfyx_model_token_uri"
	GFYX_MODEL_TOKEN_EXPIRETIME = "gfyx_model_token_expire_time"
	GFYX_MODEL_API_APPID        = "gfyx_model_api_appid"
	GFYX_MODEL_API_KEY          = "gfyx_model_api_key"
	//建模接口认证失败的关键字：认证失败：pub接口认证失败!
	GFYX_MODEL_API_AUTHENTICATION_FAIL_MESSAGE_KEYWORD = "认证失败"
	//缓存key
	GFYX_MODEL_TOKEN_CACHE_KEY  = "building_platform:token"
)

type GfyxModelApiHttpClient struct {
	client *HttpClientTool
}

type Exception struct {
	HTML string `json:"html"`
	Message string `json:"message"`
	Type string `json:"type"`
}
type Error struct {
	Exception Exception `json:"exception"`
	Type string `json:"type"`
}
type GfyxModelResponse struct {
	Error Error `json:"error"`
	Success bool `json:"success"`
}

// 获取建模平台的域名
// getHost
func getHost() string {
	var apiHost = os.Getenv(GFYX_MODEL_API_HOST)
	if apiHost != "" {
		return apiHost
	} else {
		return stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GFYX_MODEL_API_HOST, "")
	}
}

// 是否缓存token
func readCache() bool {
	// 当本地开发的时候不使用缓存
	var apiHost = os.Getenv(GFYX_MODEL_API_HOST)
	return apiHost == ""
}

// NewGfyxModelApiHttpClient
// 采购平台供应商开放服务http client工具类
func NewGfyxModelApiHttpClient() *GfyxModelApiHttpClient {
	return &GfyxModelApiHttpClient{
		client: New(),
	}
}

// Get
// 建模java api服务Get请求
func (c *GfyxModelApiHttpClient) Get(ctx context.Context, path string, params map[string]interface{}, header map[string]string) *GfyxModelApiHttpClient {
	url := getHost() + path
	url = JoinQueryStr(url, params)

	header = c.setCommonHeader(ctx, header)

	c.client = c.client.Get(ctx, url, header)
	c.tokenFailGetRetry(ctx, url, header)
	return c
}

// Post
// 建模java api服务Post请求
func (c *GfyxModelApiHttpClient) Post(ctx context.Context, path string, params map[string]interface{}, header map[string]string) *GfyxModelApiHttpClient {
	url := getHost() + path

	header = c.setCommonHeader(ctx, header)
	c.client = c.client.Post(ctx, url, params, header)
	c.tokenFailPostRetry(ctx, header, url, params)
	return c
}

// token失效时接口重试
func (c *GfyxModelApiHttpClient) tokenFailGetRetry(ctx context.Context, url string, header map[string]string) {
	gfyxModelResponse := &GfyxModelResponse{}
	_, _, _ = c.client.EndStruct(gfyxModelResponse)
	// 鉴权失败
	if gfyxModelResponse.Error.Type == "system" && strings.Index(gfyxModelResponse.Error.Exception.Message, GFYX_MODEL_API_AUTHENTICATION_FAIL_MESSAGE_KEYWORD) != -1 {
		// 删除缓存重试
		c.clearCacheToken(ctx)
		header = c.setCommonHeader(ctx, header)
		c.client = c.client.Get(ctx, url, header)
	}
}

// token失效时接口重试
func (c *GfyxModelApiHttpClient) tokenFailPostRetry(ctx context.Context, header map[string]string, url string, params map[string]interface{}) {
	gfyxModelResponse := &GfyxModelResponse{}
	_, _, _ = c.client.EndStruct(gfyxModelResponse)
	// 鉴权失败
	if gfyxModelResponse.Error.Type == "system" && strings.Index(gfyxModelResponse.Error.Exception.Message, GFYX_MODEL_API_AUTHENTICATION_FAIL_MESSAGE_KEYWORD) != -1 {
		// 删除缓存重试
		c.clearCacheToken(ctx)
		header = c.setCommonHeader(ctx, header)
		c.client = c.client.Post(ctx, url, params, header)
	}
}

func (c *GfyxModelApiHttpClient) setCommonHeader(ctx context.Context, header map[string]string) map[string]string {
	isClear := !readCache()
	token, err := c.GetToken(ctx, isClear)

	if err != nil {
		stark.Logger.Errorf(ctx, "建模获取token失败，err:%+v", err)
	}

	tenantCode := ctx.Value(authorize.TENANT_CODE_KEY)
	if tenantCode != nil {
		header["tenantCode"] = tenantCode.(string)
	} else {
		stark.Logger.Warnf(ctx, "租户code不存在")
		header["tenantCode"] = ""
	}
	header["logging_user_token"] = token
	header["Content-Type"] = "application/json"

	return header
}

type GfyxTokenResult struct {
	Success bool `json:"success"`
	Result  struct {
		Token string `json:"token"`
	} `json:"result"`
}

func (c *GfyxModelApiHttpClient) getCurrentTenantInfo(ctx context.Context) (string, string) {
	tenantCodeCtx := ctx.Value(authorize.TENANT_CODE_KEY)
	userCodeCtx := ctx.Value(authorize.TENANT_USER_CODE)
	tenantCode := ""
	userCode := ""
	if tenantCodeCtx != nil {
		tenantCode = tenantCodeCtx.(string)
	}
	if userCodeCtx != nil {
		userCode = userCodeCtx.(string)
	}
	return tenantCode, userCode
}

// 清除缓存的token
func (c *GfyxModelApiHttpClient) clearCacheToken(ctx context.Context) {
	cacheKey := GFYX_MODEL_TOKEN_CACHE_KEY
	tenantCode, userCode := c.getCurrentTenantInfo(ctx)
	if tenantCode != "" {
		cacheKey = cacheKey + ":" + tenantCode
	}
	if userCode != "" {
		cacheKey = cacheKey + ":" + userCode
	}
	redisClient, _ := cache.Redis()
	//删缓存
	redisClient.Del(ctx, cacheKey)
}

func (c *GfyxModelApiHttpClient) GetToken(ctx context.Context, isClear bool) (string, error) {
	tenantCode, userCode := c.getCurrentTenantInfo(ctx)

	//缓存key拼接
	cacheKey := GFYX_MODEL_TOKEN_CACHE_KEY
	if tenantCode != "" {
		cacheKey = cacheKey + ":" + tenantCode
	}
	if userCode != "" {
		cacheKey = cacheKey + ":" + userCode
	}

	//读缓存
	redisClient, _ := cache.Redis()
	if isClear != true {
		token, err := redisClient.Get(ctx, cacheKey)
		if err != nil {
			stark.Logger.Errorf(ctx, "建模平台读缓存失败，err: %+v", err)
			return "", err
		} else if token != "" {
			return token, nil
		}
	}

	//组装header
	header := map[string]string{}
	key := stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GFYX_MODEL_API_APPID, "")
	secret := stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GFYX_MODEL_API_KEY, "")
	header["appid"] = key
	header["appkey"] = secret
	header["Content-Type"] = "application/json"
	if tenantCode != "" {
		header["tenantCode"] = tenantCode
		header["_my-skyline-yunerp-tenantcode_"] = tenantCode
	}
	//组装请求参数
	params := map[string]interface{}{}
	if userCode != "" {
		params["userCode"] = userCode
	}

	//发起请求
	var result GfyxTokenResult
	url := getHost() + stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GFYX_MODEL_TOKEN_URI, "")
	client := c.client.Post(ctx, url, params, header)

	//处理请求结果
	resp, _, errs := client.EndStruct(&result)
	if len(errs) > 0 {
		stark.Logger.Errorf(ctx, "建模get token失败，err:%+v", errs)
		return "", errs[0]
	}
	stark.Logger.Infof(ctx, "建模get token, response: %+v, data:%+v", resp, result)
	if result.Success == false {
		stark.Logger.Errorf(ctx, "建模get token失败，result:%+v", result)
		return "", errors.New("获取建模token失败")
	}
	if result.Result.Token == "" {
		stark.Logger.Errorf(ctx, "建模get token 失败，token为空")
		return "", errors.New("获取建模token失败")
	}

	//设置缓存
	expireTimeStr := stark.Configer.(stark.ApolloConfigerIface).GetStringValue(GFYX_MODEL_TOKEN_EXPIRETIME, "")
	expireTime := 0
	expireTime, _ = strconv.Atoi(expireTimeStr)
	if expireTime <= 0 {
		expireTime = 7200
	}
	_, err := redisClient.Set(ctx, cacheKey, result.Result.Token, true, expireTime)
	if err != nil {
		stark.Logger.Errorf(ctx, "建模 set token 缓存失败，err:%+v", err)
	}

	return result.Result.Token, nil
}

// EndStruct
// convert interface(v) to struct
func (c *GfyxModelApiHttpClient) EndStruct(v interface{}) (gorequest.Response, []byte, []error) {
	return c.client.EndStruct(v)
}

func (c *GfyxModelApiHttpClient) GetJmError(str interface{}) error {
	gfyxModelResponse := &GfyxModelResponse{}
	//将 map 转换为指定的结构体
	err := mapstructure.Decode(str, gfyxModelResponse)
	if err != nil {
		return err
	}
	if gfyxModelResponse.Error.Exception.Message != "" {
		return errors.New(gfyxModelResponse.Error.Exception.Message)
	}
	return nil
}
